<?php
namespace pays\Gateways\wechatpay;
//微信支付
class WechatSDK {
    /**
     * 
     * 统一下单，WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayConfigInterface $config  配置对象
     * @param WxPayUnifiedOrder $inputObj
     * @param int $timeOut
     * @return 成功时返回，其他抛异常
     */
    public static function unifiedOrder($config, $inputObj, $timeOut = 6) {
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //检测必填参数
        //公众账号ID
        $inputObj["appid"] = $config["app_id"];
        //商户号
        $inputObj["mch_id"] = $config["mch_id"];
        //终端ip
        $inputObj["spbill_create_ip"] = $_SERVER['REMOTE_ADDR'];
        //随机字符串
        $inputObj["nonce_str"] = self::getNonceStr();
        //签名
        $inputObj["sign"] = self::MakeSign($inputObj, $config);
        //转XML
        $xml = self::ToXml($inputObj);
        $response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
        $result = self::FromXml($response);
        if($inputObj["trade_type"] == "JSAPI"){
            $timeStamp = time();
            $package = "prepay_id=".$result["prepay_id"];
            //JSAPI参与签名的参数为：appId、timeStamp、nonceStr、package、signType，
            $param = array(
                "appId"=>$config["app_id"],
                "timeStamp"=>(string)$timeStamp,
                "nonceStr"=>self::getNonceStr(),
                "package"=>$package,
                "signType"=>$config["sign_type"]
            );
            $param["paySign"] = self::MakeSign($param,$config);
            return $param;
        }elseif ($inputObj["trade_type"] == "NATIVE") {
            if ($result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") {
                return $result["code_url"];
            } else {
                return $result;
            }
        }elseif($inputObj["trade_type"] == "APP"){
			//appid，partnerid，prepayid，noncestr，timestamp，package。注意：package的值格式为Sign=WXPay
			$timeStamp = time();
            $package = "Sign=WXPay";
            //输出参数列表
			$prePayParams =array();
			$prePayParams = array(
				'appid' => $result['appid'],
				'partnerid' => $result['mch_id'],
				'prepayid' => $result['prepay_id'],
				'noncestr' => $result['nonce_str'],
				'package' => $package,
				'timestamp' => $timeStamp
			);
			$prePayParams["sign"] = self::MakeSign($prePayParams,$config);
            return $prePayParams;
		}
    }
    /**
     * 
     * 查询订单，WxPayOrderQuery中out_trade_no、transaction_id至少填一个
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayConfigInterface $config  配置对象
     * @param WxPayOrderQuery $inputObj
     * @param int $timeOut
     * @throws WxPayException
     * @return 成功时返回，其他抛异常
     */
    public static function orderQuery($config, $inputObj, $timeOut = 6) {
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        //检测必填参数
        $inputObj["appid"] = $config["app_id"]; //公众账号ID
        $inputObj["mch_id"] = $config["mch_id"]; //商户号
        $inputObj["nonce_str"] = self::getNonceStr(); //随机字符串
        $inputObj["sign"] = self::MakeSign($inputObj, $config); //签名
        $xml = self::ToXml($inputObj);
        $response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
        $result = self::FromXml($response);
        return $result;
    }
    /*
     * 订单退款
     */
    public static function refund($config, $input, $timeOut = 5) {
        $url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        $inputObj["appid"] = $config["app_id"];
        $inputObj["mch_id"] = $config["mch_id"];
        $inputObj["nonce_str"] = self::getNonceStr();
        $inputObj["out_trade_no"] = $input["order_sn"];
        $inputObj["out_refund_no"] = $input["order_sn"];
        $inputObj["total_fee"] = $input["total_fee"] * 100;
        $inputObj["refund_fee"] = $input["refund_fee"] * 100;
        $inputObj["sign"] = self::MakeSign($inputObj, $config);
        //转XML
        $xml = self::ToXml($inputObj);
        $response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
        $result = self::FromXml($response);
        return $result;
    }
    
    /**
    转账到个人
    */
    public static function transfers($config, $input, $timeOut = 5){
        $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
        $inputObj['mch_appid'] = $config["app_id"];
        $inputObj["mchid"] = $config["mch_id"];
        $inputObj["nonce_str"] = self::getNonceStr();
        $inputObj["partner_trade_no"] = $input["order_sn"];
        $inputObj["openid"] = $input["openid"];
        $inputObj["check_name"] = "NO_CHECK";
        $inputObj["amount"] = $input["total_fee"] * 100;
        $inputObj["desc"] = isset($input["desc"])?$input["desc"]:'微信转账收款';
        $inputObj["spbill_create_ip"] = "IP.IP.IP.IP";
        $inputObj["sign"] = self::MakeSign($inputObj, $config);
        //转XML
        $xml = self::ToXml($inputObj);
        $response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
        $result = self::FromXml($response);
        return $result;
    }
	

    /**
     * 以post方式提交xml到对应的接口url
     * 
     * @param WxPayConfigInterface $config  配置对象
     * @param string $xml  需要post的xml数据
     * @param string $url  url
     * @param bool $useCert 是否需要证书，默认不需要
     * @param int $second   url执行超时时间，默认30s
     */
    private static function postXmlCurl($config, $xml, $url, $useCert = false, $second = 30) {
        $ch = curl_init();
        $curlVersion = curl_version();
        $ua = "WXPaySDK/3.0.9 (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion['version'] . " " . $config["mch_id"];
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        $proxyHost = "0.0.0.0";
        $proxyPort = 0;
        //如果有配置代理这里就设置代理
        if ($proxyHost != "0.0.0.0" && $proxyPort != 0) {
            curl_setopt($ch, CURLOPT_PROXY, $proxyHost);
            curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        if(stripos($url,"https://")!==FALSE){
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验
        }else{
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
        }
        curl_setopt($ch, CURLOPT_USERAGENT, $ua);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        if ($useCert == true) {
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            //证书文件请放入服务器的非web目录下
            $sslCertPath = "";
            $sslKeyPath = "";
            $sslCertPath = __DIR__ . '/key/'.$this->cert_path;
            if (!file_exists($sslCertPath)) {
                die("Wechat Cert is not exist.");
            }
            $sslKeyPath = __DIR__ . '/key/'.$this->key_path;
            if (!file_exists($sslKeyPath)) {
                die("Wechat Key is not exist.");
            }
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            die("curl出错，错误码:$error");
        }
    }

    /**
     * 
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    public static function getNonceStr($length = 32) {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 输出xml字符
     *
     * */
    public static function ToXml($param) {
        $xml = "<xml>";
        foreach ($param as $key => $val) {
            if (is_numeric($val)) {
                $xml.="<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml.="<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
    public static function FromXml($xml) {
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $param = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $param;
    }

    /**
     * 生成签名
     */
    public static function MakeSign($param, $config) {
        //签名步骤一：按字典序排序参数
        ksort($param);
        $string = self::ToUrlParams($param);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $config["api_key"];
        //签名步骤三：MD5加密或者HMAC-SHA256
        if ($config["sign_type"] == "MD5") {
            $string = md5($string);
        } else if ($config["sign_type"] == "HMAC-SHA256") {
            $string = hash_hmac("sha256", $string, $config["api_key"]);
        } else {
            die("签名类型不支持！");
        }
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     */
    public static function ToUrlParams($param) {
        $buff = "";
        foreach ($param as $k => $v) {
            if ($k != "sign" && $v != "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

}
